#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# anomaly_detector.py - 异常模式检测模块

import re
import os
import subprocess
import json
from datetime import datetime, timedelta
from typing import Dict, List


class AnomalyDetector:
    def __init__(self, log_paths: Dict = None):
        """初始化异常检测器"""
        self.default_log_paths = {
            "apache_access": "/var/log/httpd/access_log",
            "apache_error": "/var/log/httpd/error_log",
            "system": "/var/log/messages"
        }
        self.log_paths = log_paths or self.default_log_paths
        self.anomaly_patterns = self._load_anomaly_patterns()

    def _load_anomaly_patterns(self) -> Dict:
        """加载异常模式规则"""
        return {
            "access_anomalies": [
                # 频繁404可能表示扫描行为
                {"pattern": r"\"GET .+?\" 404 \d+", "threshold": 50, "time_window": 60, "risk": "high",
                 "description": "短时间内大量404错误，可能存在目录扫描"},
                # 频繁500错误可能表示应用漏洞
                {"pattern": r"\"POST .+?\" 500 \d+", "threshold": 10, "time_window": 60, "risk": "high",
                 "description": "短时间内大量500错误，可能存在应用程序缺陷"},
                # 可疑User-Agent
                {"pattern": r"\"(Nikto|sqlmap|w3af|Nessus|Masscan)\"", "threshold": 1, "time_window": 3600,
                 "risk": "medium", "description": "检测到扫描工具访问痕迹"}
            ],
            "error_anomalies": [
                {"pattern": r"Segmentation fault", "threshold": 1, "time_window": 3600, "risk": "critical",
                 "description": "Apache进程段错误，可能存在严重漏洞"},
                {"pattern": r"Out of memory", "threshold": 3, "time_window": 600, "risk": "high",
                 "description": "内存耗尽，可能存在内存泄漏或配置不当"},
                {"pattern": r"Permission denied", "threshold": 10, "time_window": 60, "risk": "medium",
                 "description": "频繁权限错误，可能存在文件权限配置问题"}
            ],
            "system_anomalies": [
                {"pattern": r"httpd.*? killed", "threshold": 1, "time_window": 3600, "risk": "high",
                 "description": "Apache进程被杀死，可能存在崩溃或OOM killer干预"},
                {"pattern": r"Failed to start The Apache HTTP Server", "threshold": 3, "time_window": 3600,
                 "risk": "critical", "description": "Apache服务启动失败，服务不可用"}
            ]
        }

    def _get_recent_logs(self, log_file: str, hours: int = 24) -> List[str]:
        """获取最近指定小时内的日志"""
        if not os.path.exists(log_file):
            return []

        # 使用日志轮转安全读取
        try:
            with open(log_file, 'r', errors='ignore') as f:
                # 移动到文件末尾并向前读取最后10000行（防止大文件处理）
                f.seek(0, os.SEEK_END)
                pos = f.tell()
                lines = []
                while pos > 0 and len(lines) < 10000:
                    pos -= 1
                    f.seek(pos, os.SEEK_SET)
                    if f.read(1) == '\n':
                        line = f.readline()
                        lines.append(line)
                lines.reverse()
                return lines
        except Exception as e:
            print(f"读取日志文件 {log_file} 失败: {str(e)}")
            return []

    def _detect_pattern_in_logs(self, logs: List[str], patterns: List[Dict]) -> List[Dict]:
        """检测日志中符合异常模式的记录"""
        results = []
        for pattern_info in patterns:
            pattern = re.compile(pattern_info["pattern"])
            matches = [line for line in logs if pattern.search(line)]

            if len(matches) >= pattern_info["threshold"]:
                results.append({
                    "type": pattern_info["description"],
                    "count": len(matches),
                    "threshold": pattern_info["threshold"],
                    "risk_level": pattern_info["risk"],
                    "sample_logs": matches[:5]  # 取前5条样本
                })
        return results

    def detect_anomalies(self) -> Dict:
        """执行全面异常检测"""
        detection_results = {
            "scan_time": datetime.now().isoformat(),
            "anomalies": [],
            "log_files_checked": []
        }

        # 检测访问日志异常
        if os.path.exists(self.log_paths["apache_access"]):
            access_logs = self._get_recent_logs(self.log_paths["apache_access"])
            access_anomalies = self._detect_pattern_in_logs(
                access_logs,
                self.anomaly_patterns["access_anomalies"]
            )
            detection_results["anomalies"].extend([
                {**a, "source": "apache_access_log"} for a in access_anomalies
            ])
            detection_results["log_files_checked"].append(self.log_paths["apache_access"])

        # 检测错误日志异常
        if os.path.exists(self.log_paths["apache_error"]):
            error_logs = self._get_recent_logs(self.log_paths["apache_error"])
            error_anomalies = self._detect_pattern_in_logs(
                error_logs,
                self.anomaly_patterns["error_anomalies"]
            )
            detection_results["anomalies"].extend([
                {**a, "source": "apache_error_log"} for a in error_anomalies
            ])
            detection_results["log_files_checked"].append(self.log_paths["apache_error"])

        # 检测系统日志异常
        if os.path.exists(self.log_paths["system"]):
            system_logs = self._get_recent_logs(self.log_paths["system"])
            system_anomalies = self._detect_pattern_in_logs(
                system_logs,
                self.anomaly_patterns["system_anomalies"]
            )
            detection_results["anomalies"].extend([
                {**a, "source": "system_log"} for a in system_anomalies
            ])
            detection_results["log_files_checked"].append(self.log_paths["system"])

        return detection_results

    def generate_anomaly_script(self, output_path: str = "anomaly_scan.sh") -> str:
        """生成可执行的异常扫描脚本"""
        # 修复f-string中shell变量的引用方式，使用双美元符号转义
        script_content = f"""#!/bin/bash
# 自动生成的异常扫描脚本 - {datetime.now().isoformat()}
# 适用于银河麒麟系统的Apache异常检测

LOG_PATHS=(
    "{self.log_paths['apache_access']}"
    "{self.log_paths['apache_error']}"
    "{self.log_paths['system']}"
)

echo "开始异常模式扫描: $(date)"
echo "扫描日志文件: ${{LOG_PATHS[@]}}" 

# 检测404扫描行为
echo -e "\\n[检测] 短时间内大量404错误"
for log in "${{LOG_PATHS[@]}}"; do
    if [ -f "$log" ]; then
        grep -E "\"GET .+?\" 404" "$log" | tail -n 1000 | wc -l
    fi
done

# 检测错误日志中的严重错误
echo -e "\\n[检测] Apache严重错误"
for log in "${{LOG_PATHS[@]}}"; do
    if [ -f "$log" ]; then
        grep -E "Segmentation fault|Out of memory" "$log" | tail -n 20
    fi
done

echo -e "\\n扫描完成: $(date)"
"""
        with open(output_path, 'w') as f:
            f.write(script_content)
        os.chmod(output_path, 0o755)
        return output_path


if __name__ == "__main__":
    detector = AnomalyDetector()
    print("开始执行异常模式检测...")
    results = detector.detect_anomalies()

    print("\n异常检测结果:")
    print(json.dumps(results, indent=2, ensure_ascii=False))

    script_path = detector.generate_anomaly_script()
    print(f"\n异常扫描脚本已生成: {script_path}")
